package com.atguigu.service.impl;

import com.atguigu.constant.RedisConst;
import com.atguigu.entity.ProductSalePropertyKey;
import com.atguigu.entity.SkuImage;
import com.atguigu.entity.SkuInfo;
import com.atguigu.exception.SleepUtils;
import com.atguigu.mapper.ProductSalePropertyKeyMapper;
import com.atguigu.mapper.ProductSalePropertyValueMapper;
import com.atguigu.mapper.SkuSalePropertyValueMapper;
import com.atguigu.service.ProductSalePropertyKeyService;
import com.atguigu.service.SkuDetailService;
import com.atguigu.service.SkuImageService;
import com.atguigu.service.SkuInfoService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
public class SkuDetailServiceImpl implements SkuDetailService {
    @Autowired
    private SkuInfoService skuInfoService;
    @Autowired
    private SkuImageService skuImageService;
    @Resource
    private ProductSalePropertyKeyMapper salePropertyKeyMapper;
    @Resource
    private SkuSalePropertyValueMapper skuSalePropertyValueMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    //根据skuid查询skuInfo信息
    @Override
    public SkuInfo getSkuInfo(Long skuId) {
        SkuInfo skuInfo = getSkuInfoFromDb(skuId);
        //SkuInfo skuInfo = getSkuInfoFromRedis(skuId);
        //SkuInfo skuInfo = getSkuInfoFromRedisson(skuId);
        return skuInfo;
    }
    //1.通过lua脚本+redis 实现分布式锁  线程安全操作
    private SkuInfo getSkuInfoFromRedis(Long skuId) {
        String skuKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;

        //从redis中获取skuInfo信息
        SkuInfo skuInfoFromRedis = (SkuInfo)redisTemplate.opsForValue().get(skuKey);
        if (skuInfoFromRedis == null){//为空 代表数据库没有 则查DB
            //此时多线程来抢占锁  只能一个线程进入数据库 其他在外睡觉 等待
            String uuid = UUID.randomUUID().toString();
            String lockKey = RedisConst.SKUKEY_PREFIX+RedisConst.SKULOCK_SUFFIX;
            Boolean acquireLock = redisTemplate.opsForValue().setIfAbsent(lockKey, uuid);
            if (acquireLock){
                //查询DB获取数据
                SkuInfo skuInfoFromDb = getSkuInfoFromDb(skuId);
                //判断skuInfoFromDb是否有要查询的值  没有值可能会出现缓存穿透问题
                if (skuInfoFromDb == null){
                    //若为空  则返回数据为null
                    SkuInfo emptySkuInfo = new SkuInfo();
                    redisTemplate.opsForValue().set(skuKey,emptySkuInfo, RedisConst.SKUKEY_TEMPORARY_TIMEOUT, TimeUnit.SECONDS);
                    return emptySkuInfo;
                }
                //如果有值 则放入redis中一份 再返回调用者
                redisTemplate.opsForValue().set(skuKey,skuInfoFromDb,RedisConst.SKUKEY_TIMEOUT,TimeUnit.SECONDS);
                //从DB中获取到值 利用lua脚本和redis 删除lockKey
                //定义一个Lua脚本
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                redisScript.setScriptText(script);
                redisScript.setResultType(Long.class);
                redisTemplate.execute(redisScript, Arrays.asList(lockKey),uuid);
                //并且返回数据给调用者
                return skuInfoFromDb;
            }else {
                //先睡觉  再抢锁
                SleepUtils.sleepMillis(50);
                //自旋
                return getSkuInfo(skuId);
            }
        }else {
            return skuInfoFromRedis;
        }
    }

    //2.通过Redisson实现分布式锁 线程安全操作
    private SkuInfo getSkuInfoFromRedisson(Long skuId) {
        String skuKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
        //从redis中获取skuInfo信息
        SkuInfo skuInfoFromRedis = (SkuInfo)redisTemplate.opsForValue().get(skuKey);
        if (skuInfoFromRedis == null){//为空 代表数据库没有 则查DB
            //此时多线程来抢占锁  只能一个线程进入数据库 其他在外睡觉 等待
            String lockKey = RedisConst.SKUKEY_PREFIX+RedisConst.SKULOCK_SUFFIX;
            RLock lock = redissonClient.getLock(lockKey);
            try {
                boolean acquireLock = lock.tryLock(RedisConst.WAITTIN_GET_LOCK_TIME, RedisConst.LOCK_EXPIRE_TIME, TimeUnit.SECONDS);
                if (acquireLock){
                    //查询DB获取数据
                    SkuInfo skuInfoFromDb = getSkuInfoFromDb(skuId);
                    //判断skuInfoFromDb是否有要查询的值  没有值可能会出现缓存穿透问题
                    if (skuInfoFromDb == null){
                        //若为空  则返回数据为null
                        SkuInfo emptySkuInfo = new SkuInfo();
                        redisTemplate.opsForValue().set(skuKey,emptySkuInfo, RedisConst.SKUKEY_TEMPORARY_TIMEOUT, TimeUnit.SECONDS);
                        return emptySkuInfo;
                    }
                    //如果有值 则放入redis中一份 再返回调用者
                    redisTemplate.opsForValue().set(skuKey,skuInfoFromDb,RedisConst.SKUKEY_TIMEOUT,TimeUnit.SECONDS);
                    return skuInfoFromDb;
                }else {
                    //先睡觉  再抢锁
                    SleepUtils.sleepMillis(50);
                    //自旋
                    return getSkuInfo(skuId);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
        //不为空 从redis中直接返回结果
        return skuInfoFromRedis;
    }

    private SkuInfo getSkuInfoFromDb(Long skuId) {
        SkuInfo skuInfo = skuInfoService.getById(skuId);
        if (skuInfo != null){
            QueryWrapper<SkuImage> wrapper = new QueryWrapper<>();
            wrapper.eq("sku_id",skuId);
            List<SkuImage> imageList = skuImageService.list(wrapper);
            skuInfo.setSkuImageList(imageList);
        }
        return skuInfo;
    }

    //根据productId skuId 获取商品的销售属性
    @Override
    public List<ProductSalePropertyKey> getSpuSaleProperty(Long productId, Long skuId) {
        return salePropertyKeyMapper.getSpuSaleProperty(productId,skuId);
    }
    //通过spuId查询spu的商品属性 与 skuId获取对应的商品属性 的关系
    @Override
    public Map getSkuSalePropertyValueId(Long productId) {
        Map<Object, Object> resMap = new HashMap<>();
        List<Map> mapList = skuSalePropertyValueMapper.getSkuSalePropertyValueId(productId);
        if (!CollectionUtils.isEmpty(mapList)){
            for (Map map : mapList) {
                //map中key是字段 value则是实际SQL查询来的数据
                Object sale_property_value_id = map.get("sale_property_value_id");
                Object sku_id = map.get("sku_id");
                resMap.put(sale_property_value_id,sku_id);
            }
        }
        return resMap;
    }

}
